<script setup lang="ts" name="UploadForm">
import { propTypes } from '@/utils/propTypes'
import { getStrDictOptions } from '@/utils/dict'
import { DICT_TYPE } from '@/enums/dictEnums'
import { InfraResourceUploadStatusEnum } from '@/enums/infraEnums'
import type { UploadProps, UploadRawFile } from 'element-plus'
import { getAcceptSize, fileExtList, getMimetype, getFileExt, AcceptSizeType, filesizeFormatter, getDurationByMedia, getBase64ByFile } from '@/utils/file/helper'
import { FolderApi } from '@/api/infra/resource/library'
import { defaultProps, handleTree, treeLevel, treeToAscending } from '@/utils/tree'
import { buildUUID } from '@/utils/random'
import { cloneDeep } from 'lodash-es'
import { FileApi } from '@/api/infra/resource/library'
import { isSuccess } from '@/utils/axios/helper'
import FolderForm from './FolderForm.vue'
interface FileItem {
  thumbUrl?: string
  name: string
  size: string | number
  mimetype?: string
  ext?: string
  percent: number
  file: File
  status?: number
  uuid: string
  responseData?: any
  index?: number
}

const emit = defineEmits(['success', 'init', 'updateFolder']) // 定义 success 事件，用于操作成功后的回调
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗

const props = defineProps({
  folderList: propTypes.array.def([])
})

const visible = ref(false) // 弹窗的是否展示
const formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用
const formData = ref({
  folderId: undefined as number | undefined,
  mimetype: undefined as any
})
const formRef = ref() // 表单 Ref

// ========== 上传相关 ==========
const fileList: any = ref([])
const config = ref<AcceptSizeType>() // 系统资源配置
const acceptConfig = ref('') // 允许的资源扩展名
const isUploading = ref(false) // 是否正在上传
const isAutoClose = ref(false) // 是否自动关闭弹窗
const successList: any = ref([])
const errorList: any = ref([])
const collapseKeys = ref<number[]>([]) // 已折叠的节点 id 数组
const folderTree = ref<Tree[]>([])
const isUploadSuccess = ref(false)
const isUpdateFolder = ref(false)
const folderId = ref(undefined)

watch(
  () => props.folderList,
  (v: Tree[]) => {
    folderTree.value = v
  },
  {
    immediate: true
  }
)

/** 处理文件夹节点被展开 */
const handleNodeExpand = async row => {
  const index = collapseKeys.value.findIndex(item => item === row.id)
  if (index !== -1) {
    collapseKeys.value.splice(index, 1)
  }
}

/** 处理文件夹节点被折叠 */
const handleNodeCollapse = async row => {
  collapseKeys.value.push(row.id)
}

/** 处理树形结构折叠时的线条显示隐藏状态 */
const isCollapse = id => {
  return collapseKeys.value.some(item => item === id)
}

/** 获得文件夹树 */
const getTree = async () => {
  try {
    const res = await FolderApi.list()
    folderTree.value = [{
      id: 0,
      parentId: undefined,
      name: t('infra.resource.library', 'title'),
      children: treeToAscending(handleTree(res))
    }]
    treeLevel(folderTree.value, false, [], 0)
    isUpdateFolder.value = true
  } finally {
  }
}

/** 添加/修改操作 */
const folderFormRef = ref()
const openForm = (type: string) => {
  folderFormRef.value.open(type)
}

/** 打开弹窗 */
const open = async (id, mimetype) => {
  visible.value = true
  resetForm()
  // 设置数据
  folderId.value = id
  formData.value.folderId = id
  formData.value.mimetype = mimetype ? mimetype : 'all'
  // 获得角色拥有的菜单集合
  formLoading.value = true
  try {
    config.value = await getAcceptSize()
    mimetypeChange(formData.value.mimetype)
  } finally {
    formLoading.value = false
  }
}
defineExpose({ open }) // 提供 open 方法，用于打开弹窗

// 资源类型改变事件
const mimetypeChange = (v) => {
  if (v === 'all') {
    acceptConfig.value = config.value?.accept as string
  } else {
    acceptConfig.value = config.value?.config![formData.value.mimetype].accept
  }
}

// 关闭弹窗
const close = () => {
  if (isUploading.value) {
    message.warning(t('upload', 'uploadWait'))
    return false
  } else {
    visible.value = false
  }
}

/** 重置表单 */
const resetForm = () => {
  formData.value = {
    folderId: undefined as number | undefined,
    mimetype: undefined as any
  }
  folderId.value = undefined
  isUploading.value = false
  isUploadSuccess.value = false
  isUpdateFolder.value = false
  fileList.value = []
  successList.value = []
  errorList.value = []
  isAutoClose.value = false
  collapseKeys.value = []
  formRef.value?.resetFields()
}

/** 基于名字过滤 */
const filterNode = (name: string, data: Tree) => {
  if (!name) return true
  if (!data.name) return false
  return data.name.includes(name)
}

// 上传弹窗的删除事件
const handleFileRemove = (record: FileItem) => {
  const res = cloneDeep(fileList.value)
  const index = res.findIndex((item) => item.uuid === record.uuid)
  index !== -1 && res.splice(index, 1)
  fileList.value = res
  if (record.status === 4 || record.status === 5) {
    errorList.value.splice(0, 1)
  }
}

// 文件上传之前判断
const beforeUpload: UploadProps['beforeUpload'] = async (file: UploadRawFile) => {
  let fileExtension = getFileExt(file.name)
  let fileMimetype = getMimetype(file.name)
  let fileConfig = config.value
  if (config.value?.config) {
    fileConfig = config.value?.config[fileMimetype]
  }
  const isAcceptFile = acceptConfig.value.split(',').some((ext: string) => {
    if (ext.indexOf(fileMimetype)) return true
    return !!(fileExtension && ext.indexOf(fileExtension) > -1)
  })
  if (!isAcceptFile) {
    message.error(t('upload', 'accept', [acceptConfig.value, t('upload', fileMimetype)]))
    return false
  }
  const isAllowSize = fileConfig?.size === 0 || file.size < (fileConfig?.size as number)
  if (!isAllowSize) {
    message.error(t('upload', 'size', [fileConfig?.sizeText]))
    return false
  }
  const fileSize = filesizeFormatter(file.size)
  const commonItem = {
    uuid: buildUUID(),
    file,
    size: fileSize,
    name: file.name,
    percent: 0,
    status: InfraResourceUploadStatusEnum.WAITING, // 1:等待上传，2：正在上传，3：上传成功，4：上传失败, 5: 文件重复
    mimetype: fileMimetype,
    ext: fileExtension,
    parameter: getDurationByMedia(file)
  }
  // 生成图片缩略图
  if (fileMimetype === 'image') {
    getBase64ByFile(file).then(({ result: thumbUrl }) => {
      const res = cloneDeep(fileList.value)
      res.push({
        thumbUrl,
        ...commonItem
      })
      fileList.value = res
    })
  } else {
    const newFileList = [...fileList.value, commonItem]
    fileList.value = newFileList
  }
  return false
}


// 开始上传按钮事件
const handleUpload = async () => {
  try {
    errorList.value = []
    isUploading.value = true
    // 只上传不是成功状态的
    const uploadFileList = fileList.value.filter((item) => item.status !== InfraResourceUploadStatusEnum.SUCCESS) || []
    const data = await Promise.all(
      uploadFileList.map((item) => {
        return uploadByFile(item)
      })
    )
    isUploading.value = false
    data.forEach((item) => {
      if (item.success) {
        successList.value.push(item)
      }
    })
    // 生产环境:抛出错误
    errorList.value = data.filter((item: any) => !item.success)
    if (successList.value.length > 0) {
      isUploadSuccess.value = true
    }
    if (fileList.value.length === successList.value.length) {
      message.success(t('upload', 'success'))
      if (isAutoClose.value) {
        close()
      }
    }
  } catch (e) {
    isUploading.value = false
    throw e
  }
}

// 单个文件上传调用上传接口
const uploadByFile = async (item) => {
  try {
    item.status = InfraResourceUploadStatusEnum.UPLOADING
    const resBody = {
      file: item.file,
      name: item.name,
      folderId: formData.value.folderId,
      mimetype: item.mimetype,
      duration: undefined,
      width: undefined,
      height: undefined
    }
    if (item.parameter) {
      if (item.parameter.duration) {
        resBody.duration = item.parameter.duration
      }
      if (item.parameter.width) {
        resBody.width = item.parameter.width
      }
      if (item.parameter.height) {
        resBody.height = item.parameter.height
      }
    }
    const result: any = await FileApi.uploadProgress(resBody, (progressEvent: ProgressEvent) => {
      const complete = ((progressEvent.loaded / progressEvent.total) * 100) | 0
      item.percent = complete
    })
    if (isSuccess(result)) {
      item.status = InfraResourceUploadStatusEnum.SUCCESS
      item.responseData = result.data
      return {
        success: true,
        error: null
      }
    } else {
      item.status = InfraResourceUploadStatusEnum.ERROR
      return {
        success: false,
        error: result.msg
      }
    }
  } catch (e) {
    item.status = InfraResourceUploadStatusEnum.ERROR
    return {
      success: false,
      error: e
    }
  }
}

const submitForm = () => {
  if (isUploading.value) {
    message.warning(t('upload', 'uploadWait'))
    return false
  } else {
    if (isUpdateFolder.value) {
      if (isUploadSuccess.value) {
        if (folderId.value === formData.value.folderId) {
          emit('init')
        } else {
          emit('updateFolder')
        }
      } else {
        emit('updateFolder')
      }
    } else {
      if (isUploadSuccess.value) {
        if (folderId.value === formData.value.folderId) {
          emit('success')
        }
      }
    }
    visible.value = false
  }
}
</script>

<template>
  <Dialog v-model="visible" :title="t('infra.resource.library', 'upload')" type="card" append-to-body height="60vh" width="960px" :before-close="close">
    <ContentFormTable padding="0">
      <template #header>
        <div class="upload-header">
          <el-text>{{ t('infra.resource.library.file', 'upload') }}</el-text>
          <ElSelect v-model="formData.mimetype" class="!w-120px" @change="mimetypeChange">
            <ElOption :label="t('common', 'all')" value="all" />
            <ElOption v-for="dict in getStrDictOptions(DICT_TYPE.INFRA_RESOURCE_MIMETYPE)" :key="dict.value" :label="dict.label" :value="dict.value" />
          </ElSelect>
          <el-text>{{ t('infra.resource.library.file', 'to') }}</el-text>
          <ElTreeSelect
            ref="treeRef"
            v-model="formData.folderId"
            class="tree-level !w-160px"
            :data="folderTree"
            :props="defaultProps"
            node-key="id"
            check-strictly
            :render-after-expand="false"
            filterable
            default-expand-all
            :filter-node-method="filterNode"
            :expand-on-click-node="false"
            :check-on-click-node="true"
            @node-expand="handleNodeExpand"
            @node-collapse="handleNodeCollapse">
            <template #default="{ node, data }">
              <span class="item-tree-node">
                <span>{{ node.label }}</span>
                <template v-for="item in data.level">
                  <span v-if="!data.hideLine.includes(item)" :key="item" :class="['item-tree-node-line-vertical', data.isLast && item == data.level ? 'is-last' : '', data.isFirst && data.level === 1 ? 'is-first' : '']" :style="{ left: 18 * (item - 1) + 12 + 'px' }" />
                </template>
                <span v-if="!data.isLeaf && !isCollapse(data.id)" class="item-tree-node-line-vertical is-first" :style="{ left: 18 * (data.level) + 12 + 'px' }" />
                <span class="item-tree-node-line-horizontal" :style="{ left: 18 * (data.level - 1) + 12 + 'px' }" />
              </span>
            </template>
          </ElTreeSelect>
          <ElButton type="primary" link @click="openForm('create')">{{ t('infra.resource.library.folder', 'add') }}</ElButton>
        </div>
      </template>
      <template #content>
        <ElUpload v-if="fileList.length === 0" :show-file-list="false" :accept="acceptConfig" multiple :before-upload="beforeUpload" drag class="upload-content">
          <div class="upload-image">
            <SvgIcon icon="ri:upload-cloud-line" :size="64" />
          </div>
          <div class="upload-remark">
            <el-text type="info">{{ t('infra.resource.library.file', 'drag') }}</el-text>
          </div>
          <div class="upload-tips">
            <p v-if="formData.mimetype === 'all'" class="tips line" v-html="config?.helpTextLine" />
            <p v-if="formData.mimetype !== 'all' && config?.config" class="tips line" v-html="config?.config[formData.mimetype].helpTextLine" />
            <p class="tips line">
              <el-text>{{ t('infra.resource.library.file', 'multiply') }}</el-text>
            </p>
          </div>
        </ElUpload>
        <ElTable v-else border :data="fileList">
          <ElTableColumn prop="thumbUrl" :label="t('upload', 'title')" width="90" align="center">
            <template #default="scope">
              <div class="image-wrap">
                <ElImage v-if="scope.row.mimetype === 'image'" :src="scope.row.thumbUrl" fit="fill" lazy />
                <SvgIcon v-else-if="scope.row.mimetype === 'video'" icon="ci:filetype-video" size="32" />
                <SvgIcon v-else-if="scope.row.mimetype === 'audio'" icon="ci:filetype-audio" size="32" />
                <template v-else>
                  <svg-icon v-if="scope.row.ext && fileExtList.includes(scope.row.ext)" :icon="'ci:filetype-' + scope.row.ext" size="64" />
                  <SvgIcon v-else icon="ci:filetype-unknown" size="32" />
                </template>
              </div>
            </template>
          </ElTableColumn>
          <ElTableColumn prop="name" :label="t('upload', 'fileName')">
            <template #default="scope">
              <div class="file-name">
                <p>{{ scope.row.name }}</p>
                <ElProgress :percentage="scope.row.percent" :status="scope.row.status === InfraResourceUploadStatusEnum.SUCCESS ? 'success' : scope.row.status === InfraResourceUploadStatusEnum.ERROR ? 'exception' : ''" />
              </div>
            </template>
          </ElTableColumn>
          <ElTableColumn prop="size" :label="t('upload', 'fileSize')" width="120" align="center" />
          <ElTableColumn prop="status" width="160" :label="t('common', 'status')" align="center">
            <template #default="scope">
              <div class="file-status">
                <dict-tag :type="DICT_TYPE.INFRA_RESOURCE_UPLOAD_STATUS" :value="scope.row.status" />
              </div>
            </template>
          </ElTableColumn>
          <ElTableColumn prop="operate" :label="t('common', 'operate')" width="100" align="center">
            <template #default="scope">
              <div class="file-operate">
                <ElLink v-if="scope.row.status === InfraResourceUploadStatusEnum.WAITING || scope.row.status === InfraResourceUploadStatusEnum.ERROR" :underline="false" type="danger" @click="handleFileRemove(scope.row)">{{ t('action', 'delete') }}</ElLink>
              </div>
            </template>
          </ElTableColumn>
        </ElTable>
        <!-- 表单弹窗：添加/修改/重命名/移动到 -->
        <FolderForm ref="folderFormRef" :folder-list="folderTree" @success="getTree" />
      </template>
    </ContentFormTable>
    <template #footer>
      <div class="flex flex-1 items-center justify-end">
        <ElCheckbox v-if="fileList.length > 0" v-model="isAutoClose" size="small">{{ t('infra.resource.library.file', 'close') }}</ElCheckbox>
        <el-button class="ml-12px" @click="close">{{ t('action', 'cancel') }}</el-button>
        <ElUpload v-if="fileList.length > 0" :show-file-list="false" :accept="acceptConfig" multiple :before-upload="beforeUpload" class="ml-12px">
          <ElButton>{{ t('infra.resource.library.file', 'more') }}</ElButton>
        </ElUpload>
        <ElButton v-if="fileList.length > 0 && fileList.length !== successList.length" type="primary" :disabled="isUploading" class="ml-12px" @click="handleUpload">
          {{ errorList.length > 0 ? t('upload', 'reUploadFailFile') : t('upload', 'startUpload') }}
        </ElButton>
        <el-button v-if="isUpdateFolder || isUploadSuccess" :disabled="isUploading" type="primary" class="ml-12px" @click="submitForm">{{ t('action', 'confirm') }}</el-button>
      </div>
    </template>
  </Dialog>
</template>

<style scoped lang="scss">
.image-wrap {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  margin: 0 auto;
  border: 1px solid var(--el-border-color);

  .el-image {
    max-width: 40px;
    max-height: 40px;

    :deep(.el-image__inner) {
      max-width: 100%;
      max-height: 38px;
    }
  }
}

.file-name {
  p {
    .el-icon {
      font-size: 16px !important;
      color: var(--el-color-danger);
    }
  }
}

.file-operate {
  .el-link {
    margin: 3px;
  }
}

.el-alert--success,
.el-alert--warning {
  margin-top: var(--el-space);
  margin-bottom: 6px;
}

.el-alert--success{

  &.is-light {
    border: 1px solid var(--el-color-success);
  }
}

.el-alert--warning {

  &.is-light {
    border: 1px solid var(--el-color-warning);
  }
}

.upload-header {
  margin-bottom: 6px;

  .el-select {
    margin: 0 12px;
  }
}

.upload-content{
  display: flex;
  flex-direction: column;
  flex: 1;

  :deep(.el-upload) {
    display: flex!important;
    flex-direction: column;
    flex: 1;

    .el-upload-dragger {
      display: flex;
      flex-direction: column;
      flex: 1;
      align-items: center;
      justify-content: center;
      width: 100%;
      padding: var(--el-space);
      background-color: var(--el-fill-color-lighter);

      &:hover {
        border-color: var(--el-color-primary)
      }
    }
  }

  :deep(.el-upload-list) {
    margin-top: 0;
  }

  .upload-image {
    .el-icon {
      color: var(--el-color-primary);
    }
  }

  .upload-remark {
    margin: 15px 0;
    font-size: 16px;
  }
}

.upload-tips {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  margin: 0 6px 6px;

  .tips {
    margin: 12px 0 0;

    &.line{
      display: flex;
    }

    :deep(.el-text){
      display: flex;
      font-size: 12px;
      line-height: 1.4;
      color: var(--el-text-color-secondary);

      span{
        margin: 0 6px;
        color: var(--el-color-primary);
      }
    }
  }

  p {
    font-size: 12px;
    line-height: 24px;
    color: var(--el-text-color-secondary);
  }
}

.el-table {
  width: 100%;
  height: 100%;
}
</style>
